<script lang="tsx">
import {
  defineComponent,
  ref,
  computed,
  type ExtractPropTypes,
  type PropType,
  watch,
  unref,
  inject
} from 'vue'
import {
  type FormRules,
  type FormItemRule,
  ElForm,
  ElFormItem,
  type FormItemProp,
  dayjs,
  type FormInstance,
  type FormValidateCallback
} from 'element-plus'
import { cloneDeep, isNil, merge, omit } from 'lodash-es'
import {
  oButton,
  oInput,
  oSelect,
  oCheckbox,
  oRadio,
  oImage,
  oDatepicker,
  oTag,
  oColorpicker,
  oCascader,
  oSwitch,
  oSlider,
  oLink,
  oText,
  oUpload,
  oDownload
} from './index'
import {
  $AONE_LAN,
  type SimpleKeyValueObject,
  getValOrFnResult,
  assert,
  type BaseType,
  toNumber,
  toTime
} from './utils'
import type { ValidateFieldsError } from 'async-validator'
import type { Arrayable } from 'element-plus/es/utils'

export type FormColumnSupportType =
  | 'input'
  | 'select'
  | 'checkbox'
  | 'radio'
  | 'button'
  | 'picture'
  | 'upload'
  | 'datepicker'
  | 'time'
  | 'money'
  | 'integer'
  | 'text'
  | 'tag'
  | 'colorpicker'
  | 'cascader'
  | 'switch'
  | 'slider'
  | 'carousel'
  | 'tree'
  | 'link'
  | 'menuinput'
export interface iFormColumnType {
  prop?: string
  context?: Object
  label?: String | Function
  labelWidth?: String
  rules?: Array<FormItemRule>
  require?: Boolean
  requireMsg?: string
  visiable?: Boolean | Function
  comtype?: string
  value?: Array<string | number> | number | string
  startTimeField?: string
  endTimeField?: string
  formItemClassName?: string | Array<String> | SimpleKeyValueObject | Function
  formItemStyle?: string | SimpleKeyValueObject | Function
  className?: string | Array<String> | SimpleKeyValueObject | Function
  style?: string | SimpleKeyValueObject | Function
  renderHeader?: Function
  render?: Function
  renderFooter?: Function
  type?: string
  onClick?: Function
  options?: Array<BaseType>
  row?: SimpleKeyValueObject
  [key: string]: BaseType
}
export interface iFormOptionsProps {
  columns: Array<iFormColumnType>
  inline?: boolean
  submit?: Boolean
  cancel?: Boolean
  reject?: Boolean
  isDialog?: Boolean
  labelWidth?: Boolean
  rules?: FormRules
  validate?: Boolean
  unmounted?: Function
}
export const oFormProps = {
  formOptions: {
    required: true,
    type: Object as PropType<iFormOptionsProps>
  },
  formData: {
    type: Object as PropType<SimpleKeyValueObject>
  }
}
export type oFormPropsType = ExtractPropTypes<typeof oFormProps>
// export type oFormItemPropsType = ExtractPropTypes<typeof oFormItemProps>
export default defineComponent({
  props: oFormProps,
  components: {
    ElForm,
    ElFormItem,
    oButton,
    oInput,
    oSelect,
    oCheckbox,
    oRadio,
    oImage,
    oDatepicker,
    oTag,
    oColorpicker,
    oCascader,
    oSwitch,
    oSlider,
    oLink,
    oText,
    oUpload,
    oDownload
  },
  setup(props: oFormPropsType, context) {
    const formData = props.formData || inject<SimpleKeyValueObject>('formData')! // 感叹号是一定存在
    const originFormData = Object.create(null)
    let storeFormItems: Array<JSX.Element> = []

    const refForm = ref<FormInstance>()
    const formOptionsSync = computed(() => {
      const formOptions = merge(
        {
          labelWidth: '80px',
          rules: {}
        },
        // cloneDeep(oFormProps.formOptions),
        unref(props.formOptions)
      )
      formOptions.columns.forEach((it: iFormColumnType) => {
        // assert(it.prop, 'prop必传')
        const context = it.context
        it.label = getValOrFnResult(it.label)
        it.labelWidth =
          isNil(it.labelWidth) && it.label
            ? `${formOptions.labelWidth}`
            : `${it.labelWidth || '0px'}`
        const rules = it.rules || new Array<FormItemRule>()
        if (it.require) {
          rules.push({
            required: true,
            message: it.requireMsg
              ? it.requireMsg
              : $AONE_LAN.plsInput + it.label,
            trigger: 'blur'
          })
        }
        if (formOptions.validate && it.prop) {
          formOptions.rules[it.prop] = rules
        }
        // for (const itKey in it) {
        //   if (typeof it[itKey] == "function") {
        //     it[itKey] = it[itKey].bind(context);
        //   }
        // }
      })
      return formOptions
    })
    const formColumns = computed(() => {
      return formOptionsSync.value.columns.filter(
        it => it && getValOrFnResult(it.visiable) !== false
      )
    })
    const renderFormItems = () => {
      const formItems: Array<JSX.Element> = []
      formColumns.value.forEach((it, idx) => {
        const inProps: iFormColumnType = {
          key: `key-${it.prop}-${idx}`,
          class: [
            'one-form-item-comtype',
            'one-form-item-comtype-' + (it.comtype || 'input'),
            'one-form-item-comtype-' + it.prop,
            getValOrFnResult(it.className)
          ],
          style: getValOrFnResult(it.style),
          ...omit(it, ['className', 'style']),
          inline: !!formOptionsSync.value.inline,
          formData: formData
        }
        const jsxItem = (bodyRender?: Function) => {
          bodyRender = inProps.render || bodyRender
          return (
            <ElFormItem
              prop={inProps.prop}
              labelWidth={`${inProps.labelWidth}`}
              rules={inProps.rules}
              class={[
                'one-form-item',
                'one-form-item-' + inProps.prop,
                getValOrFnResult(inProps.formItemClassName)
              ]}
              style={getValOrFnResult(inProps.formItemStyle)}
              v-slots={{ label: () => getValOrFnResult(inProps.label) }}
            >
              {it.renderHeader?.(inProps)}
              {bodyRender?.(inProps.row, inProps)}
              {it.renderFooter?.(inProps)}
            </ElFormItem>
          )
        }
        switch (it.comtype) {
          case 'vnode': {
            formItems.push(jsxItem())
            break
          }
          case 'button': {
            const label = inProps.label || inProps.value
            delete inProps.label
            const formItemClassNameArray = ['one-nopadding']
            // if (!inProps.labelWidth || inProps.labelWidth == '0px') {
            //   formItemClassNameArray.push('one-form-item-nolabel')
            // }
            inProps.formItemClassName = formItemClassNameArray.join(' ')
            formItems.push(
              jsxItem(() => <oButton {...inProps}>{label}</oButton>)
            )
            break
          }
          case 'br': {
            // 换行
            formItems.push(
              <div style={`${inProps.style}`} class={inProps.class}></div>
            )
            break
          }
          case 'text': {
            // 纯文本
            formItems.push(jsxItem(() => <oText {...inProps} />))
            break
          }
          case 'checkbox': {
            formItems.push(jsxItem(() => <oCheckbox {...inProps} />))
            break
          }
          case 'select': {
            formItems.push(jsxItem(() => <oSelect {...inProps} />))
            break
          }
          case 'radio': {
            formItems.push(jsxItem(() => <oRadio {...inProps} />))
            break
          }
          case 'picture': {
            formItems.push(jsxItem(() => <oImage {...inProps} />))
            break
          }
          case 'datepicker': {
            formItems.push(jsxItem(() => <oDatepicker {...inProps} />))
            break
          }
          case 'colorpicker': {
            formItems.push(jsxItem(() => <oColorpicker {...inProps} />))
            break
          }
          case 'cascader': {
            formItems.push(jsxItem(() => <oCascader {...inProps} />))
            break
          }
          case 'switch': {
            formItems.push(jsxItem(() => <oSwitch {...inProps} />))
            break
          }
          case 'slider': {
            formItems.push(jsxItem(() => <oSlider {...inProps} />))
            break
          }
          case 'upload': {
            formItems.push(jsxItem(() => <oUpload {...inProps} />))
            break
          }
          case 'download': {
            formItems.push(jsxItem(() => <oDownload {...inProps} />))
            break
          }
          case 'link': {
            formItems.push(jsxItem(() => <oLink {...inProps} />))
            break
          }
          case 'tag': {
            formItems.push(jsxItem(() => <oTag {...inProps} />))
            break
          }
          case 'time': {
            const v = toTime(inProps.value as number, 'YYYY-MM-DD HH:mm:ss')
            formItems.push(jsxItem(() => <>{v}</>))
            break
          }
          case 'money': {
            const v = toNumber(inProps.value as number | string, 2)
            formItems.push(jsxItem(() => <>{v}</>))
            break
          }
          case 'integer': {
            const v = toNumber(inProps.value as number | string)
            formItems.push(jsxItem(() => <>{v}</>))
            break
          }
          case 'input':
          default: {
            // 默认input
            formItems.push(jsxItem(() => <oInput {...inProps} />))
            break
          }
        }
      })
      storeFormItems = formItems
      return formItems
    }
    watch(
      formOptionsSync,
      data => {
        data.columns.filter(Boolean).forEach(it => {
          if (it.prop && !Reflect.has(formData, it.prop)) {
            // select 多选时，和checkbox两种组件是数组
            if (
              (it.comtype == 'select' && it.multiple) ||
              it.comtype == 'checkbox'
            ) {
              if (!Array.isArray(it.value)) {
                it.value = it.value !== undefined ? [it.value] : []
              }
            }
            // selectday 日期默认0
            if (it.comtype == 'selectday') {
              if (it.value === undefined) {
                it.value = 0
              }
              if (it.startTimeField) {
                formData.startTimeField = ''
                originFormData.startTimeField = ''
              }
              if (it.endTimeField) {
                formData.endTimeField = ''
                originFormData.endTimeField = ''
              }
            }
            formData[it.prop] = it.value
            originFormData[it.prop] = it.value
          }
        })
      },
      { immediate: true, deep: true }
    )

    const eventData = inject<SimpleKeyValueObject>('eventData')! // 感叹号是一定存在
    // 重置
    eventData &&
      watch(
        () => eventData.reset,
        () => {
          if (!eventData.reset) return
          Object.keys(formData).forEach(prop => {
            delete formData[prop]
          })
          Object.keys(originFormData).forEach(prop => {
            formData[prop] = cloneDeep(originFormData[prop])
          })
          eventData.reset = false
        }
      )
    //  const formRef = ref<InstanceType<typeof ElForm>>();
    // 表单验证
    context.expose({
      onValidate(
        onSubmit: Function,
        onValidError: Function,
        fields: Array<string> = []
      ) {
        const callback: FormValidateCallback = async (
          valid: boolean,
          fields
        ) => {
          if (valid) {
            let v
            const toString = Object.prototype.toString
            // 判断onSubmit是异步函数
            if (
              ['[object AsyncFunction]', '[object Promise]'].includes(
                toString.call(onSubmit)
              )
            ) {
              v = await onSubmit?.(formData)
            } else {
              v = onSubmit?.(formData)
            }
            // 不为false就关闭弹出
            if (v !== false) {
              context.emit('close')
            }
          } else {
            onValidError?.(fields, formData)
          }
        }
        if (fields.length == 0) {
          refForm.value?.validate(callback)
        } else {
          refForm.value?.validateField(fields, callback)
        }
      },
      clearValidate(fields: Array<string> = []) {
        refForm.value?.clearValidate(fields)
      }
    })
    return () => {
      const _renderItems = renderFormItems()
      return _renderItems.length ? (
        <ElForm
          class={['one-form']}
          model={formData}
          rules={formOptionsSync.value.rules}
          inline={formOptionsSync.value.inline}
          ref={refForm}
        >
          {_renderItems}
        </ElForm>
      ) : null
    }
  }
})
</script>
<style lang="scss" scoped></style>
